/*****************************************************************************
 * Copyright (C) Stacy Curl. All rights reserved.                            *
 * ------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the BSD      *
 * style license a copy of which has been included with this distribution in *
 * the LICENSE.txt file.                                                     *
 *****************************************************************************/
package picounit.features.verify.constraint;

import java.lang.reflect.Array;

// TODO: Refactor to use EqualUtil
public class EqualTo extends ModifiableConstraint<EqualTo> {
	private final Object equalTo;
	private Stringer stringer;

	public EqualTo(Object equalTo, Modifier modifier, Stringer stringer) {
		super(modifier);

		this.equalTo = equalTo;
		
		this.stringer = stringer;
	}

	@Override
	public boolean evaluate(Object value) {
		return bothNull(equalTo, value) ||
			(neitherNull(equalTo, value) &&
				sameReference(equalTo, value) || equals(modify(value), modify(equalTo)));
	}

	private boolean neitherNull(Object left, Object right) {
		return left != null && right != null;
	}

	private boolean bothNull(Object left, Object right) {
		return right == null && left == null;
	}

	private boolean sameReference(Object left, Object right) {
		return left == right;
	}

	private boolean equals(Object equalTo, Object value) {
		return arrayEquals(equalTo, value) || ordinaryEquals(equalTo, value);
	}

	private boolean arrayEquals(Object left, Object right) {
		if (!isArray(left) || !isArray(right)) {
			return false;
		}
		
		int length = Array.getLength(left);
		
		if (Array.getLength(right) != length) {
			return false;
		}
		
		for (int index = 0; index < length; index++ ) {
			if (!elementsEqual(Array.get(left, index), Array.get(right, index))) {
				return false;
			}
		}
		
		return true;
	}

	private boolean ordinaryEquals(Object equalTo, Object value) {
		return value.equals(equalTo);
	}

	private boolean isArray(Object object) {
		return object != null && object.getClass().isArray();
	}

	private boolean elementsEqual(Object element, Object otherElement) {
		return (element == null && otherElement == null) ||
			(element != null && otherElement != null && equals(element, otherElement));
	}

	@Override
	public String describeFailureImpl() {
		return "is not equal to <" + stringer.toString(equalTo) + ">";
	}

	///////////////////////////////////////////////////////////////////////////////////////////////
	// Equal methods
	///////////////////////////////////////////////////////////////////////////////////////////////
	
	@Override
	public boolean equalTo(EqualTo equalTo) {
		return this.equalTo.equals(equalTo.equalTo);
	}
	
	///////////////////////////////////////////////////////////////////////////////////////////////
	// Object methods
	///////////////////////////////////////////////////////////////////////////////////////////////

	@Override
	public int hashCode() {
		return equalTo.hashCode();
	}

	@Override
	public String toString() {
		return "Is equal to: " + equalTo;
	}
}
